using System;
using System.Collections.Generic;
using System.Text;
using System.Reflection;
using AntEater.ComponentModel.BuildElements;
using AntEater.ComponentModel;

namespace AntEater.Runtime.Services.Transformation
{

    /// <summary>
    /// Provides a Factory for Transfomers. 
    /// </summary>
    internal static class BuildElementTransformerFactory
    {

        private static Dictionary<System.Xml.XPath.XPathNodeType,Type> _xPathNodeTypeFallbackTransformers;

        /// <summary>
        /// Gets the X path node type fallback transformers.
        /// </summary>
        /// <value>The X path node type fallback transformers.</value>
        public static Dictionary<System.Xml.XPath.XPathNodeType, Type> XPathNodeTypeFallbackTransformers {
            get {
                if (_xPathNodeTypeFallbackTransformers == null) {
                    LoadTransformers();
                }
                return _xPathNodeTypeFallbackTransformers;
            }
        }

        private static Dictionary<string, Type> _elementTransformers;

        /// <summary>
        /// Gets the element transformers.
        /// </summary>
        /// <value>The element transformers.</value>
        public static Dictionary<string, Type> ElementTransformers {
            get {
                if (_elementTransformers == null) {
                    LoadTransformers();
                }
                return _elementTransformers;
            }
        }

        private static Dictionary<Type, Type> _buildElementTransformers;

        /// <summary>
        /// Gets the build element transformers.
        /// </summary>
        /// <value>The build element transformers.</value>
        public static Dictionary<Type, Type> BuildElementTransformers {
            get {
                if (_buildElementTransformers == null) {
                    LoadTransformers();
                }
                return _buildElementTransformers;
            }
        }

        private static Dictionary<Type, Type> _elementFallbackTransformers;

        /// <summary>
        /// Gets the element fallback transformers.
        /// </summary>
        /// <value>The element fallback transformers.</value>
        public static Dictionary<Type, Type> ElementFallbackTransformers {
            get {
                if (_elementFallbackTransformers == null) {
                    LoadTransformers();
                }
                return _elementFallbackTransformers;
            }
        }

        /// <summary>
        /// Gets the transformer.
        /// </summary>
        /// <param name="buildElementType">Type of the build element.</param>
        /// <param name="transformerContext">The transformer context.</param>
        /// <returns></returns>
        public static IBuildElementTransformer GetTransformer(Type buildElementType,object transformerContext) {
            
            IBuildElementTransformer transformer = null;

            if (BuildElementTransformers.ContainsKey(buildElementType)) {
                transformer = Activator.CreateInstance(BuildElementTransformers[buildElementType],transformerContext) as IBuildElementTransformer;
            }

            return transformer;

        }

        /// <summary>
        /// Gets the transformer.
        /// </summary>
        /// <param name="elementName">Name of the element.</param>
        /// <param name="nodeType">Type of the node.</param>
        /// <param name="parentTransformerType">Type of the parent transformer.</param>
        /// <param name="transformerContext">The transformer context.</param>
        /// <returns></returns>
        public static IBuildElementTransformer GetTransformer(string elementName, System.Xml.XPath.XPathNodeType nodeType, Type parentTransformerType, object transformerContext) {

            IBuildElementTransformer transformer = null;

            if(nodeType == System.Xml.XPath.XPathNodeType.Element) {
                transformer = GetTransformerByElement(elementName,parentTransformerType,transformerContext);
            }

            if (transformer == null) {
                transformer = GetFallbackTransformer(nodeType,transformerContext);
            }

            return transformer;

        }

        /// <summary>
        /// Gets the fallback transformer.
        /// </summary>
        /// <param name="nodeType">Type of the node.</param>
        /// <param name="transformerContext">The transformer context.</param>
        /// <returns></returns>
        private static IBuildElementTransformer GetFallbackTransformer(System.Xml.XPath.XPathNodeType nodeType, object transformerContext) {
            IBuildElementTransformer transformer = null;

            if (XPathNodeTypeFallbackTransformers.ContainsKey(nodeType)) {
                Type transformerType = XPathNodeTypeFallbackTransformers[nodeType];

                if (transformerType != null) {
                    transformer = Activator.CreateInstance(transformerType,transformerContext) as IBuildElementTransformer;
                }
            }

            return transformer;
        }

        /// <summary>
        /// Gets the transformer by element.
        /// </summary>
        /// <param name="elementName">Name of the element.</param>
        /// <param name="parentTransformerType">Type of the parent transformer.</param>
        /// <param name="transformerContext">The transformer context.</param>
        /// <returns></returns>
        private static IBuildElementTransformer GetTransformerByElement(string elementName, Type parentTransformerType, object transformerContext)
        {
            IBuildElementTransformer transformer = null;

            BuildElementTransformerAttribute parentTransformerAttribute = null;

            if (parentTransformerType != null) {
                parentTransformerAttribute =
                    ComponentModel.Utilities.GetTypeAttributeFromType<BuildElementTransformerAttribute>(parentTransformerType);
            }

            //Check if the Type of Transformer is provided by the Parent Transformer
            if (parentTransformerAttribute != null) {
                //If the Transformer of the Parent Node defines the Transformer of its Child Nodes
                //then take it.
                if (parentTransformerAttribute.ChildBuildElementTransformer != null) {
                    transformer = Activator.CreateInstance(parentTransformerAttribute.ChildBuildElementTransformer,transformerContext) as IBuildElementTransformer;
                }
            }

            //If no Transformer is defined by the Parent Transformer, get the Transformer by
            //the Elment Name
            if (transformer == null) {

                if (ElementTransformers.ContainsKey(elementName)) {
                    Type transformerType = ElementTransformers[elementName];

                    if (transformerType != null) {
                        transformer = Activator.CreateInstance(transformerType,transformerContext) as IBuildElementTransformer;
                    }
                }
            }

            //If Transformer is defined neither by the Parent Transformer nor by the Element Name,
            //then try to Find an Fallback Element Transformer for the Parent Transformer.
            if (transformer == null) {

                if (ElementFallbackTransformers.ContainsKey(parentTransformerType)) {
                    Type transformerType = ElementFallbackTransformers[parentTransformerType];

                    if (transformerType != null) {
                        transformer = Activator.CreateInstance(transformerType,transformerContext) as IBuildElementTransformer;
                    }
                }
            }

            return transformer;
        }

        /// <summary>
        /// Loads the transformers.
        /// </summary>
        private static void LoadTransformers() {
            _elementTransformers = new Dictionary<string, Type>();
            _xPathNodeTypeFallbackTransformers = new Dictionary<System.Xml.XPath.XPathNodeType, Type>();
            _elementFallbackTransformers = new Dictionary<Type, Type>();
            _buildElementTransformers = new Dictionary<Type, Type>();

            BuildElementTransformerAttribute attribute;

            Assembly currentAsssembly = Assembly.GetExecutingAssembly();

            Type[] types = currentAsssembly.GetTypes();

            foreach (Type type in types) {
                if (type.IsSubclassOf(typeof(BuildElementTransformerBase))) {
                    attribute = ComponentModel.Utilities.GetTypeAttributeFromType<BuildElementTransformerAttribute>(type);
                    if (attribute != null) {

                        //Add Transformer Type to BuildElementTransformer Dict, when
                        //the Transformer transforms an Build Element of an specific Type.
                        if (attribute.BuildElementType != null) {
                            _buildElementTransformers.Add(attribute.BuildElementType, type);
                        }

                        //Add Transformer Type to ElementTransformer Dict, when
                        //the Transformer transforms an XmlElement with an specific Element Name.
                        if (attribute.XPathNodeType == System.Xml.XPath.XPathNodeType.Element) {
                            //Add only Transformers that Transform NonGenericBuildElements
                            if (!attribute.BuildElementAttribute.HasDynamicElementName) {
                                _elementTransformers.Add(attribute.BuildElementAttribute.Name, type);
                            }
                        }

                        //If the Transformer Type is an Fallback Transformer, then add it to
                        //FallbackTransformer Dict.
                        if (attribute.IsFallbackForXPathNodeType) {
                            _xPathNodeTypeFallbackTransformers.Add(attribute.XPathNodeType, type);
                        }

                        //If the Transformer Type is an Fallback Transformer for ChildElements of an Element,
                        //then add it to the ElementChildsFallback
                        if (attribute.FallbackForChildElementsOf != null) {
                            _elementFallbackTransformers.Add(attribute.FallbackForChildElementsOf, type);
                        }
                    }
                }
            }
        }
    }
}
